#include <map>
#include <variant>

#include <logger.h>
#include <napi_call.h>
#include <node_api.h>

napi_value GetInt(napi_env env, int i)
{
    napi_value i32;
    NAPI_CALL(env, napi_create_int32(env, i, &i32));
    return i32;
}

bool CoerceToBool(napi_env env, napi_value val)
{
    napi_value boolean;
    bool boolVal = false;
    NAPI_CALL_RET(env, napi_coerce_to_bool(env, val, &boolean), false);
    NAPI_CALL_RET(env, napi_get_value_bool(env, boolean, &boolVal), false);
    return boolVal;
}

double CoerceToNumber(napi_env env, napi_value val)
{
    napi_value d64;
    double doubleVal = 0;
    NAPI_CALL_RET(env, napi_coerce_to_number(env, val, &d64), 0);
    NAPI_CALL_RET(env, napi_get_value_double(env, d64, &doubleVal), 0);
    return doubleVal;
}

std::string CoerceToString(napi_env env, napi_value val)
{
    napi_value str;
    char strbuf[32] = {};
    size_t strbuflen = 0;
    NAPI_CALL(env, napi_coerce_to_string(env, val, &str));
    NAPI_CALL(env, napi_get_value_string_utf8(env, str, strbuf, sizeof(strbuf), &strbuflen));
    return strbuf;
}

std::string ToString(napi_valuetype valtype)
{
    std::map<napi_valuetype, std::string> toStringMap = {
        {napi_undefined, "napi_undefined"},
        {napi_null, "napi_null"},
        {napi_boolean, "napi_boolean"},
        {napi_number, "napi_number"},
        {napi_string, "napi_string"},
        {napi_symbol, "napi_symbol"},
        {napi_object, "napi_object"},
        {napi_function, "napi_function"},
        {napi_external, "napi_external"},
        {napi_bigint, "napi_bigint"},
    };
    if (toStringMap.find(valtype) == toStringMap.end()) {
        return "unknown";
    }

    return toStringMap[valtype];
}

void ObjectOperation(napi_env env, napi_value val)
{
    napi_value global = nullptr;
    napi_value arrayCtor = nullptr;
    napi_value numberCtor = nullptr;
    NAPI_CALL_RET(env, napi_get_global(env, &global),);
    NAPI_CALL_RET(env, napi_get_named_property(env, global, "Array", &arrayCtor),);
    NAPI_CALL_RET(env, napi_get_named_property(env, global, "Number", &numberCtor),);

    VALUE_LOGGER(CoerceToBool(env, val));
    VALUE_LOGGER(CoerceToNumber(env, val));
    VALUE_LOGGER(CoerceToString(env, val));

    napi_valuetype valtype = napi_undefined;
    NAPI_CALL_RET(env, napi_typeof(env, val, &valtype),);
    VALUE_LOGGER(valtype);
    VALUE_LOGGER(ToString(valtype));

    bool instanceofArray = false;
    NAPI_CALL_RET(env, napi_instanceof(env, val, arrayCtor, &instanceofArray),);
    VALUE_LOGGER(instanceofArray);

    bool instanceofNumber = false;
    NAPI_CALL_RET(env, napi_instanceof(env, val, numberCtor, &instanceofNumber),);
    VALUE_LOGGER(instanceofNumber);

    bool isArray = false;
    napi_is_array(env, val, &isArray);
    VALUE_LOGGER(isArray);

    bool isDate = false;
    napi_is_date(env, val, &isDate);
    VALUE_LOGGER(isDate);

    bool isError = false;
    napi_is_error(env, val, &isError);
    VALUE_LOGGER(isError);
}

/* napi_value */
NAPI_MODULE_INIT(/* napi_env env, napi_value exports */)
{
    napi_value val;
    NAPI_CALL(env, napi_create_array_with_length(env, 3, &val));
    NAPI_CALL(env, napi_set_element(env, val, 0, GetInt(env, 1)));
    NAPI_CALL(env, napi_set_element(env, val, 1, GetInt(env, 2)));
    NAPI_CALL(env, napi_set_element(env, val, 2, GetInt(env, 3)));
    ObjectOperation(env, val);
    // CoerceToBool(env, val): 'true'
    // CoerceToNumber(env, val): 'nan'
    // CoerceToString(env, val): '1,2,3'
    // valtype: '6'
    // ToString(valtype): 'napi_object'
    // instanceofArray: 'true'
    // instanceofNumber: 'false'
    // isArray: 'true'
    // isDate: 'false'
    // isError: 'false'

    NAPI_CALL(env, napi_create_array(env, &val));
    ObjectOperation(env, val);
    // CoerceToBool(env, val): 'true'
    // CoerceToNumber(env, val): '0'
    // CoerceToString(env, val): ''
    // valtype: '6'
    // ToString(valtype): 'napi_object'
    // instanceofArray: 'true'
    // instanceofNumber: 'false'
    // isArray: 'true'
    // isDate: 'false'
    // isError: 'false'

    NAPI_CALL(env, napi_get_undefined(env, &val));
    ObjectOperation(env, val);
    // CoerceToBool(env, val): 'false'
    // CoerceToNumber(env, val): 'nan'
    // CoerceToString(env, val): 'undefined'
    // valtype: '0'
    // ToString(valtype): 'napi_undefined'
    // instanceofArray: 'false'
    // instanceofNumber: 'false'
    // isArray: 'false'
    // isDate: 'false'
    // isError: 'false'

    NAPI_CALL(env, napi_create_int32(env, 456, &val));
    ObjectOperation(env, val);
    // CoerceToBool(env, val): 'true'
    // CoerceToNumber(env, val): '456'
    // CoerceToString(env, val): '456'
    // valtype: '3'
    // ToString(valtype): 'napi_number'
    // instanceofArray: 'false'
    // instanceofNumber: 'false'
    // isArray: 'false'
    // isDate: 'false'
    // isError: 'false'

    NAPI_CALL(env, napi_create_date(env, 789, &val));
    ObjectOperation(env, val);
    // CoerceToBool(env, val): 'true'
    // CoerceToNumber(env, val): '789'
    // CoerceToString(env, val): 'Thu Jan 01 1970 08:00:00 GMT+08'
    // valtype: '6'
    // ToString(valtype): 'napi_object'
    // instanceofArray: 'false'
    // instanceofNumber: 'false'
    // isArray: 'false'
    // isDate: 'true'
    // isError: 'false'

    return exports;
}
